// This file is part of Substrate.

// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// 	http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! Some configurable implementations as associated type for the substrate runtime.

use crate::{Authorship, Balances, NegativeImbalance};
use frame_support::traits::{Currency, OnUnbalanced};

pub struct Author;
impl OnUnbalanced<NegativeImbalance> for Author {
    fn on_nonzero_unbalanced(amount: NegativeImbalance) {
        Balances::resolve_creating(&Authorship::author(), amount);
    }
}

#[cfg(test)]
mod multiplier_tests {
    use pallet_transaction_payment::{Multiplier, TargetedFeeAdjustment};
    use sp_runtime::{assert_eq_error_rate, traits::Convert, FixedPointNumber};

    use crate::{
        constants::{currency::*, time::*},
        AdjustmentVariable, MinimumMultiplier, Runtime, RuntimeBlockWeights as BlockWeights, System,
        TargetBlockFullness, TransactionPayment,
    };
    use frame_support::weights::{DispatchClass, Weight, WeightToFeePolynomial};

    fn max_normal() -> Weight {
        BlockWeights::get()
            .get(DispatchClass::Normal)
            .max_total
            .unwrap_or_else(|| BlockWeights::get().max_block)
    }

    fn min_multiplier() -> Multiplier {
        MinimumMultiplier::get()
    }

    fn target() -> Weight {
        TargetBlockFullness::get() * max_normal()
    }

    // update based on runtime impl.
    fn runtime_multiplier_update(fm: Multiplier) -> Multiplier {
        TargetedFeeAdjustment::<Runtime, TargetBlockFullness, AdjustmentVariable, MinimumMultiplier>::convert(fm)
    }

    // update based on reference impl.
    fn truth_value_update(block_weight: Weight, previous: Multiplier) -> Multiplier {
        let accuracy = Multiplier::accuracy() as f64;
        let previous_float = previous.into_inner() as f64 / accuracy;
        // bump if it is zero.
        let previous_float = previous_float.max(min_multiplier().into_inner() as f64 / accuracy);

        // maximum tx weight
        let m = max_normal() as f64;
        // block weight always truncated to max weight
        let block_weight = (block_weight as f64).min(m);
        let v: f64 = AdjustmentVariable::get().to_fraction();

        // Ideal saturation in terms of weight
        let ss = target() as f64;
        // Current saturation in terms of weight
        let s = block_weight;

        let t1 = v * (s / m - ss / m);
        let t2 = v.powi(2) * (s / m - ss / m).powi(2) / 2.0;
        let next_float = previous_float * (1.0 + t1 + t2);
        Multiplier::from_fraction(next_float)
    }

    fn run_with_system_weight<F>(w: Weight, assertions: F)
    where
        F: Fn() -> (),
    {
        let mut t: sp_io::TestExternalities =
            frame_system::GenesisConfig::default().build_storage::<Runtime>().unwrap().into();
        t.execute_with(|| {
            System::set_block_consumed_resources(w, 0);
            assertions()
        });
    }

    #[test]
    fn truth_value_update_poc_works() {
        let fm = Multiplier::saturating_from_rational(1, 2);
        let test_set = vec![
            (0, fm.clone()),
            (100, fm.clone()),
            (1000, fm.clone()),
            (target(), fm.clone()),
            (max_normal() / 2, fm.clone()),
            (max_normal(), fm.clone()),
        ];
        test_set.into_iter().for_each(|(w, fm)| {
            run_with_system_weight(w, || {
                assert_eq_error_rate!(
                    truth_value_update(w, fm),
                    runtime_multiplier_update(fm),
                    // Error is only 1 in 100^18
                    Multiplier::from_inner(100),
                );
            })
        })
    }

    #[test]
    fn multiplier_can_grow_from_zero() {
        // if the min is too small, then this will not change, and we are doomed forever.
        // the weight is 1/100th bigger than target.
        run_with_system_weight(target() * 101 / 100, || {
            let next = runtime_multiplier_update(min_multiplier());
            assert!(next > min_multiplier(), "{:?} !>= {:?}", next, min_multiplier());
        })
    }

    #[test]
    fn multiplier_cannot_go_below_limit() {
        // will not go any further below even if block is empty.
        run_with_system_weight(0, || {
            let next = runtime_multiplier_update(min_multiplier());
            assert_eq!(next, min_multiplier());
        })
    }

    #[test]
    fn time_to_reach_zero() {
        // blocks per 24h in substrate-node: 28,800 (k)
        // s* = 0.1875
        // The bound from the research in an empty chain is:
        // v <~ (p / k(0 - s*))
        // p > v * k * -0.1875
        // to get p == -1 we'd need
        // -1 > 0.00001 * k * -0.1875
        // 1 < 0.00001 * k * 0.1875
        // 10^9 / 1875 < k
        // k > 533_333 ~ 18,5 days.
        run_with_system_weight(0, || {
            // start from 1, the default.
            let mut fm = Multiplier::one();
            let mut iterations: u64 = 0;
            loop {
                let next = runtime_multiplier_update(fm);
                fm = next;
                if fm == min_multiplier() {
                    break;
                }
                iterations += 1;
            }
            assert!(iterations > 533_333);
        })
    }

    #[test]
    fn min_change_per_day() {
        run_with_system_weight(max_normal(), || {
            let mut fm = Multiplier::one();
            // See the example in the doc of `TargetedFeeAdjustment`. are at least 0.234, hence
            // `fm > 1.234`.
            for _ in 0..DAYS {
                let next = runtime_multiplier_update(fm);
                fm = next;
            }
            assert!(fm > Multiplier::saturating_from_rational(1234, 1000));
        })
    }

    #[test]
    #[ignore]
    fn congested_chain_simulation() {
        // `cargo test congested_chain_simulation -- --nocapture` to get some insight.

        // almost full. The entire quota of normal transactions is taken.
        let block_weight = BlockWeights::get().get(DispatchClass::Normal).max_total.unwrap() - 100;

        // Default substrate weight.
        let tx_weight = frame_support::weights::constants::ExtrinsicBaseWeight::get();

        run_with_system_weight(block_weight, || {
            // initial value configured on module
            let mut fm = Multiplier::one();
            assert_eq!(fm, TransactionPayment::next_fee_multiplier());

            let mut iterations: u64 = 0;
            loop {
                let next = runtime_multiplier_update(fm);
                // if no change, panic. This should never happen in this case.
                if fm == next {
                    panic!("The fee should ever increase");
                }
                fm = next;
                iterations += 1;
                let fee = <Runtime as pallet_transaction_payment::Config>::WeightToFee::calc(&tx_weight);
                let adjusted_fee = fm.saturating_mul_acc_int(fee);
                println!(
                    "iteration {}, new fm = {:?}. Fee at this point is: {} units / {} millicents, \
					{} cents, {} dollars",
                    iterations,
                    fm,
                    adjusted_fee,
                    adjusted_fee / MILLICENTS,
                    adjusted_fee / CENTS,
                    adjusted_fee / DOLLARS,
                );
            }
        });
    }

    #[test]
    fn stateless_weight_mul() {
        let fm = Multiplier::saturating_from_rational(1, 2);
        run_with_system_weight(target() / 4, || {
            let next = runtime_multiplier_update(fm);
            assert_eq_error_rate!(next, truth_value_update(target() / 4, fm), Multiplier::from_inner(100),);

            // Light block. Multiplier is reduced a little.
            assert!(next < fm);
        });

        run_with_system_weight(target() / 2, || {
            let next = runtime_multiplier_update(fm);
            assert_eq_error_rate!(next, truth_value_update(target() / 2, fm), Multiplier::from_inner(100),);
            // Light block. Multiplier is reduced a little.
            assert!(next < fm);
        });
        run_with_system_weight(target(), || {
            let next = runtime_multiplier_update(fm);
            assert_eq_error_rate!(next, truth_value_update(target(), fm), Multiplier::from_inner(100),);
            // ideal. No changes.
            assert_eq!(next, fm)
        });
        run_with_system_weight(target() * 2, || {
            // More than ideal. Fee is increased.
            let next = runtime_multiplier_update(fm);
            assert_eq_error_rate!(next, truth_value_update(target() * 2, fm), Multiplier::from_inner(100),);

            // Heavy block. Fee is increased a little.
            assert!(next > fm);
        });
    }

    #[test]
    fn weight_mul_grow_on_big_block() {
        run_with_system_weight(target() * 2, || {
            let mut original = Multiplier::zero();
            let mut next = Multiplier::default();

            (0..1_000).for_each(|_| {
                next = runtime_multiplier_update(original);
                assert_eq_error_rate!(next, truth_value_update(target() * 2, original), Multiplier::from_inner(100),);
                // must always increase
                assert!(next > original, "{:?} !>= {:?}", next, original);
                original = next;
            });
        });
    }

    #[test]
    fn weight_mul_decrease_on_small_block() {
        run_with_system_weight(target() / 2, || {
            let mut original = Multiplier::saturating_from_rational(1, 2);
            let mut next;

            for _ in 0..100 {
                // decreases
                next = runtime_multiplier_update(original);
                assert!(next < original, "{:?} !<= {:?}", next, original);
                original = next;
            }
        })
    }

    #[test]
    fn weight_to_fee_should_not_overflow_on_large_weights() {
        let kb = 1024 as Weight;
        let mb = kb * kb;
        let max_fm = Multiplier::saturating_from_integer(i128::max_value());

        // check that for all values it can compute, correctly.
        vec![
            0,
            1,
            10,
            1000,
            kb,
            10 * kb,
            100 * kb,
            mb,
            10 * mb,
            2147483647,
            4294967295,
            BlockWeights::get().max_block / 2,
            BlockWeights::get().max_block,
            Weight::max_value() / 2,
            Weight::max_value(),
        ]
        .into_iter()
        .for_each(|i| {
            run_with_system_weight(i, || {
                let next = runtime_multiplier_update(Multiplier::one());
                let truth = truth_value_update(i, Multiplier::one());
                assert_eq_error_rate!(truth, next, Multiplier::from_inner(50_000_000));
            });
        });

        // Some values that are all above the target and will cause an increase.
        let t = target();
        vec![t + 100, t * 2, t * 4].into_iter().for_each(|i| {
            run_with_system_weight(i, || {
                let fm = runtime_multiplier_update(max_fm);
                // won't grow. The convert saturates everything.
                assert_eq!(fm, max_fm);
            })
        });
    }
}
